﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.Serialization.Formatters.Binary;

namespace xxpl1.Cache
{
    [Serializable]
    class VirtualDiskSapce
    {
        private readonly int start;
        private int end;

        public VirtualDiskSapce(int start, int end)
        {
            this.start = start;
            this.end = end;
        }

        public int Start
        {
            get { return start; }
        }

        public int End
        {
            get { return end; }
        }

        public int Size
        {
            get { return end - start; }
        }

        public void Eat(VirtualDiskSapce freeDiskSapce)
        {
            end = freeDiskSapce.end;
        }

        public VirtualDiskSapce[] Born(int size)
        {
            if (size == Size)
            {
                return new VirtualDiskSapce[] { this };
            }

            VirtualDiskSapce free = new VirtualDiskSapce(start + size, end);
            end = start + size;
            return new VirtualDiskSapce[] { this, free };
        }
    }

    [Serializable]
    public class VirtualDisk : IDisposable
    {
        [NonSerializedAttribute]
        private MemoryMappedFile memoryMappedFile;

        private readonly int size;
        private readonly string name;
        private readonly Dictionary<string, VirtualDiskSapce> fileIndexs = new Dictionary<string, VirtualDiskSapce>();
        private readonly List<VirtualDiskSapce> freeDiskSapces = new List<VirtualDiskSapce>();

        public VirtualDisk(string name, int size)
        {
            this.name = name;
            this.size = size;

            using (FileStream fs = new FileStream(name + ".mdb", FileMode.Create))
            {
                fs.SetLength(size);
                fs.Close();
            }

            InitMemoryMapFile();
        }

        private void InitMemoryMapFile()
        {
            memoryMappedFile = MemoryMappedFile.CreateFromFile(name + ".mdb", FileMode.Open);

        }

        public void AddFile(string fileName, byte[] value)
        {
            VirtualDiskSapce diskSapce = FindFreeSpace(value.Length);

            if (diskSapce == null)
                return;

            using (MemoryMappedViewStream stream = memoryMappedFile.CreateViewStream(diskSapce.Start, diskSapce.Size))
            {
                stream.Write(value, 0, value.Length);
                stream.Close();

                fileIndexs.Add(fileName, diskSapce);
            }
        }

        private VirtualDiskSapce FindFreeSpace(int length)
        {
            lock (freeDiskSapces)
            {
                VirtualDiskSapce diskSapce = freeDiskSapces.Find(s => s.Size >= length);

                if (diskSapce == null)
                {
                    fileIndexs.Clear();
                    freeDiskSapces.Clear();

                    diskSapce = new VirtualDiskSapce(0, size);
                }

                VirtualDiskSapce[] spaces = diskSapce.Born(length);

                if (spaces.Length == 2) freeDiskSapces.Add(spaces[1]);

                return spaces[0];
            }
        }

        public bool ContainsFile(string key)
        {
            return fileIndexs.ContainsKey(key);
        }

        public byte[] ReadFile(string fileName)
        {
            VirtualDiskSapce diskSapce = fileIndexs[fileName];

            if (diskSapce == null)
                return null;

            using (MemoryMappedViewStream stream = memoryMappedFile.CreateViewStream(diskSapce.Start, diskSapce.Size))
            {
                byte[] content = new byte[diskSapce.Size];
                stream.Read(content, 0, diskSapce.Size);
                return content;
            }
        }

        public void DeleteFile(string fileName)
        {
            lock (freeDiskSapces)
            {
                freeDiskSapces.Add(fileIndexs[fileName]);
                fileIndexs.Remove(fileName);
            }
        }

        private void SaveToImage()
        {
            BinaryFormatter formatter = new BinaryFormatter();

            FileInfo fileInfo = new FileInfo(name + ".mdx");

            using (FileStream fs = fileInfo.OpenWrite())
            {
                formatter.Serialize(fs, this);
            }
        }

        public static VirtualDisk LoadFromImage(string filePath)
        {
            BinaryFormatter formatter = new BinaryFormatter();

            FileInfo fileInfo = new FileInfo(filePath);

            using (FileStream fs = fileInfo.OpenRead())
            {
                VirtualDisk disk = (VirtualDisk)formatter.Deserialize(fs);

                disk.InitMemoryMapFile();

                return disk;
            }
        }

        public static VirtualDisk CreateVirtualDisk(string name, int size)
        {
            VirtualDisk virtualDisk = new VirtualDisk(name, size);
            return virtualDisk;
        }

        public void Dispose()
        {
            SaveToImage();

            memoryMappedFile.Dispose();
            memoryMappedFile = null;
        }
    }
}
